package praktikum_I.blatt_II.aufgabe1;

import java.util.*;


public class Matrix_impl<E> implements Matrix_inter<E> {

    private int maxValue = Integer.MAX_VALUE;

    /////////////////////////////////
    //   Matrix als 2-dim Array    //
    ///////                 /////////

    /**
     * Addiert zwei Matrizen, die aus 2 - dimensionalen Arrays bestehen
     * @param aMatrix
     * @param oMatrix
     * @return double[][]
     */

	@Override
	public double[][] sumArray(double[][] aMatrix, double[][] oMatrix) {

		int gapNumber = aMatrix.length;
		int lineNumber = aMatrix[0].length;

		if (gapNumber != oMatrix.length || lineNumber != oMatrix[0].length) {
			throw new IllegalArgumentException(
					"Matrizen muessen vom gleichen Typ sein");
		}

		double[][] result = new double[gapNumber][lineNumber];

		for (int i = 0; i < gapNumber; i++)
			for (int j = 0; j < lineNumber; j++)
				result[i][j] = aMatrix[i][j] + oMatrix[i][j];

		return result;
	}

    /**
     * Multipliziert eine Matrix, die aus einem 2 - dimensionalen Array besteht, mit einem Skalar
     * @param aMatrix
     * @return double[][]
     */

	@Override
	public double[][] mulArray(double[][] aMatrix, int skalar) {

		int gapNumber = aMatrix.length;
		int lineNumber = aMatrix[0].length;

		double[][] result = new double[gapNumber][lineNumber];

		for (int i = 0; i < gapNumber; i++)
			for (int j = 0; j < lineNumber; j++)

				result[i][j] = aMatrix[i][j] * skalar;

		return result;
	}

    /**
     * Multipliziert zwei Matrizen, die aus 2 - dimensionalen Arrays bestehen
     * @param aMatrix
     * @param oMatrix
     * @return double[][]
     */

	@Override
	public double[][] mulArray(double[][] aMatrix, double[][] oMatrix) {

		int gapNumber = aMatrix.length;
		int lineNumber = aMatrix[0].length;

		if (gapNumber != oMatrix[0].length || lineNumber != oMatrix.length) {
			throw new IllegalArgumentException(
					"Anzahl der Spalten der Matrix A muss gleich Anzahl der Zeilen der Matrix B sein!");
		}
		double[][] result = new double[aMatrix.length][oMatrix[0].length];

		for (int i = 0; i < aMatrix.length; i++)
			for (int j = 0; j < oMatrix[0].length; j++)
				for (int k = 0; k < aMatrix[0].length; k++) // aMatrix.length
					result[i][j] += aMatrix[i][k] * oMatrix[k][j];
		return result;
	}

    /**
     * Potenziert eine Matrix, die aus einem 2 - dimensionalen Array besteht, mit einem Exponenten
     * @param aMatrix
     * @param exponent
     * @return double[][]
     */

	@Override
	public double[][] powArray(double[][] aMatrix, int exponent) {

		int gapNumber = aMatrix.length;
		int lineNumber = aMatrix[0].length;

		if (gapNumber != lineNumber) {
			throw new IllegalArgumentException(
					"Nur eine quadratische Matrix kann potenziert werden");
		}
	    return powArrayRek(aMatrix, aMatrix, exponent);
	}

    /**
     * Hilfsmethode fuer powArray()
     * @param aMatrix
     * @param oMatrix
     * @param exponent
     * @return Gibt das rekursive Ergebnis an die Hauptfunktion
     */
	
	private double[][] powArrayRek(double[][] aMatrix, double[][] oMatrix, int exponent) {
		if (exponent == 1 ) return aMatrix;
		else {
		    return powArrayRek(mulArray(aMatrix, oMatrix), oMatrix, exponent - 1);
        }
	}

    /**
     * Generiert eine zufällige Matrix
     * @param aMaxValue
     * @param set
     * @return double[][]
     */

    public double[][] createRandomMatrix(int aMaxValue, Set<Point> set){
        Random r = new Random();
        int n = r.nextInt(aMaxValue);
        double[][] result = new double[n][n];
        for (int i = 0; i <n; i++) {
            for (int j = 0; j<n; j++){
                double insert = r.nextInt(maxValue);
                Point t = new Point(i,j);
                if (set != null && set.contains(t)){
                    if (insert == 0.0)
                        do{
                            insert = r.nextInt(aMaxValue);
                            result[i][j] = insert;
                        }while (insert == 0.0);
                    else
                        result[i][j] = insert;
                } else
                    result[i][j] = insert;
            }
        }
        return result;
    }

    /**
     * Erstellt eine zufaellig generierte Matrix, wie in Aufgabe 5.gefordert
     * @param size
     * @param set
     * @return double[][]
     */

    public double[][] createMatrix(int size, Set<Point> set){
        Random r = new Random();
        double[][] result = new double[size][size];
        for (int i = 0; i <size; i++) {
            for (int j = 0; j<size; j++){
                double insert = r.nextDouble();
                Point t = new Point(i,j);
                if (set != null && set.contains(t)){
                    do{
                        result[i][j] = insert;
                    }while (insert == 0);
                } else
                    result[i][j] = insert;
            }
        }
        return result;
    }


    /////////////////////////////////
    //   Matrix mit Listen in Array//
    ///////                 /////////

    /**
     * Addiert zwei Matrizen, die aus Listen in einem Array bestehen
     * @param aMatrix
     * @param oMatrix
     * @return List<Double>[]
     */

    @Override
    public List<Double>[] sumArrayList(List<Double>[] aMatrix, List<Double>[] oMatrix) {
        int gapNumber = aMatrix.length;
        int lineNumber = aMatrix[0].size();

        if (gapNumber != oMatrix.length || lineNumber != oMatrix[0].size()) {
            throw new IllegalArgumentException(
                    "Matrizen muessen vom gleichen Typ sein");
        }

        List<Double>[] result = new ArrayList[gapNumber];

        for (int i = 0; i < gapNumber; i++) {
            result[i] = new ArrayList<Double>();
            for (int j = 0; j < lineNumber; j++)
                if (aMatrix[i].get(j) + oMatrix[i].get(j) != 0)
                    result[i].add((aMatrix[i].get(j) + oMatrix[i].get(j)));
                else
                    result[i].add(null) ;
        }
        return result;
    }

    /**
     * Multipliziert eine Matrix, die aus Listen in einem Array besteht, mit einem Skalar
     * @param aMatrix
     * @param skalar
     * @return List<Double>[]
     */

    @Override
    public List<Double>[] mulArrayList(List<Double>[] aMatrix, int skalar) {
        List<Double>[] result = new ArrayList[aMatrix.length];
        for (int i = 0; i < aMatrix.length; i++){
            result[i] = new ArrayList<Double>();
            for (int j = 0; j< aMatrix[i].size(); j++){
                result[i].add( aMatrix[i].get(j) * skalar);
            }
        }
        return result;  //To change body of implemented methods use File | Settings | File Templates.
    }

    /**
     * Multipliziert zwei Matrizen, die aus Listen in einem Array bestehen
     * @param aMatrix
     * @param oMatrix
     * @return List<Double>[]
     */

    @Override
    public List<Double>[] mulArrayList(List<Double>[] aMatrix, List<Double>[] oMatrix) {
        List<Double>[] result = new ArrayList[oMatrix.length];
        if(aMatrix.length != oMatrix[0].size())
            throw new IllegalArgumentException("Mit diesen Matrizen ist eine Multiplikation nicht moeglich");
        else{
            for (int i = 0; i < aMatrix.length; i ++){
                result[i] = new ArrayList<Double>();
                for (int j = 0; j < aMatrix[i].size(); j++){
                    double tmp = 0;
                    for (int k = 0; k < oMatrix[i].size(); k++) {
                        tmp = tmp + (aMatrix[i].get(k) * oMatrix[k].get(j));
                    }
                    if(tmp == 0) result[i].add(null);
                    else result[i].add(tmp);
                }
            }
        }
        return result;
    }

    /**
     * Potenziert eine Matrix, die aus Listen in einem Array besteht, mit einem Exponenten
     * @param aMatrix
     * @param exponent
     * @return List<Double>[]
     */

    @Override
    public List<Double>[] powArrayList(List<Double>[] aMatrix, int exponent) {
        if (exponent != 0){
            List<Double>[] nullResult = new ArrayList[aMatrix.length];
              for(int i = 0; i< aMatrix.length; i++){
                  nullResult[i] = new ArrayList<Double>();
                  for (int j = 0; j < aMatrix[i].size(); j++){
                      nullResult[i].add(1.0);
                  }
              }
               return nullResult;
        } else
        return rek_powArrayList(aMatrix, aMatrix, exponent);  //To change body of implemented methods use File | Settings | File Templates.
    }

    /**
     * Hilfsmethode fuer powArrayList()
     * @param aMatrix
     * @param oMatrix
     * @param exponent
     * @return Gibt das rekursive Ergebnis an die Hauptfunktion
     */

    private List<Double>[] rek_powArrayList(List<Double>[] aMatrix, List<Double>[] oMatrix, int exponent) {
        if (exponent == 1){
            return aMatrix;
        }else {
            return rek_powArrayList(mulArrayList(aMatrix,oMatrix), oMatrix, exponent-1);
        }
    }

    /**
     * Generiert eine zufaellige Matrix
     * @param aMaxValue
     * @param set
     * @return  List<Double>[]
     */

    public List<Double>[] createRandomMatrix_A(int aMaxValue, Set<Point> set){
        Random r = new Random();
        int n = r.nextInt(aMaxValue);
        List<Double>[] result = new ArrayList[n];
        for (int i = 0; i <n; i++) {
            result[i] = new ArrayList<Double>();
            for (int j = 0; j<n; j++){
                double insert = r.nextInt(maxValue);
                Point t = new Point(i,j);
                if (set != null && set.contains(t)){
                    if (insert == 0.0)
                        do{
                            insert = r.nextInt(aMaxValue);
                            result[i].add(insert);
                        }while (insert == 0.0);
                    else
                        result[i].add(insert);
                } else
                    result[i].add(insert);
            }
        }
        return result;
    }



    //////////////////////////
    //   Matrix mit Listen //
    ///////        /////////

    /**
     * Addiert zwei Matrizen, die aus einer Liste bestehen
     * @param aMatrix
     * @param oMatrix
     * @return List<Point>
     */
    @Override
    public List<Point> sumList(List<Point> aMatrix, List<Point> oMatrix){
        List<Point> result = new ArrayList<Point>();
        for(int i = 0; i < aMatrix.size(); i++){
            for (int j = 0; j <oMatrix.size(); j++){
                Point aPoint = aMatrix.get(i);
                Point oPoint = oMatrix.get(j);
                if (oPoint.getX() == aPoint.getX() &&  aPoint.getY() == oPoint.getY() && add(aPoint.getValue(), oPoint.getValue()) != 0) {
                    result.add(new Point(aPoint.getX(), oPoint.getY(), add(aPoint.getValue(), oPoint.getValue())));
                }
            }
        }
        return result;
    }

    /**
     * Multipliziert eine Matrix, die aus einer Liste bestehen, mit einem Skalar
     * @param aMatrix
     * @return List<Point>
     */
    @Override
    public List<Point> mulList(List<Point> aMatrix, double skalar){
        List<Point> result = new ArrayList<Point>();
        if (skalar != 0)
            for(int i = 0; i<aMatrix.size(); i++){
                Point aPoint = aMatrix.get(i);
                result.add(new Point(aPoint.getX(), aPoint.getY(), mul(skalar, aPoint.getValue())));
            }
        return result;
    }

    /**
     * Multipliziert zwei Matrizen, die aus einer Liste bestehen
     * @param aMatrix
     * @param oMatrix
     * @return List<Point>
     */
    @Override
    public List<Point> mulList(List<Point> aMatrix, List<Point> oMatrix) {
        List<Point> result = new ArrayList<Point>();
        for (int t = 1; t  < aMatrix.size(); t++){

            for (int i = 0; i < aMatrix.size(); i++){
                Point aPoint = aMatrix.get(i);
                for (int j = 0; j< oMatrix.size();j++){
                    Point oPoint = oMatrix.get(j);
                    if (aPoint.getX() == t && t == oPoint.getY())
                        out("===> Value a: " + aPoint.getValue() + " ValueB");
                        reunite(new Point(oPoint.getX(), aPoint.getY(), mul(aPoint.getValue(), oPoint.getValue())),result);

                }
            }

        }
        return result;
    }

    /**
     * Gibt das Ergebnis des Potenzierens aus der rekursiven Untermethode als
     * Liste aus
     * @param aMatrix
     * @param exponent
     * @return List<Point>
     */
    @Override
    public List<Point> powList(List<Point> aMatrix, int exponent){
        return rek_powMatrixList(aMatrix,aMatrix,exponent);
    }

    /**
     * Hilfsmethode fuer powMatrixList()
     * @param aMatrix
     * @param oMatrix
     * @return List<Point>
     */

    public List<Point> rek_powMatrixList(List<Point> aMatrix, List<Point> oMatrix, int pow){
        if(pow == 1) return aMatrix;
        else return rek_powMatrixList(mulList(aMatrix,oMatrix), oMatrix, pow-1);
    }

    /**
     * Prüft ob es schon an den IndexPunkten einen Point-Obj gibt,
     * wenn ja, wird das ergebniss hinzugefügt
     * @param newPoint
     * @param tList
     * @return  List<Point>
     */

    private List<Point> reunite(Point newPoint, List<Point> tList){
        boolean isAdd = false;
        if (tList.isEmpty())
            tList.add(newPoint);
        else{
            Point tmp;
            for (int i = 0; i < tList.size(); i++){
                Point tPoint = tList.get(i);
                if(tPoint.contain(newPoint)){
                    tPoint = tList.remove(i);
                    tmp = new Point(tPoint.getX(), tPoint.getY(), add(tPoint.getValue(), newPoint.getValue()));
                    tList.add(tmp);
                    isAdd = true;
                }
            }
            if (!isAdd) tList.add(newPoint);
        }
        return tList;
    }


    //////////////////////////
    //   Klassenmethoden    //
    ///////          /////////

    private double mul(double a, double b){
        return a*b;
    }

    private double add(double a, double b){
        return a+b;
    }

    /**
     * Gibt einen uebergebenen String auf der Konsole aus
     * @param string
     * @return void
     */

    private void out(String string) {
        System.out.println(string);
    }

    /**
     * Stellt einen 2 - dimensionalen Array als String dar
     * @param aMatrix
     * @return void
     */

    public String toStringArray(double[][] aMatrix){
        String str= "";
        str+=("Array-Matrix{");
        for (int i = 0; i < aMatrix.length; i++) {
            str+="\n";
            for (int j = 0; j < aMatrix[i].length; j++)
                str+=("[" + i + "] [" + j + "] = " + aMatrix[i][j]);
        }
        str+=(" }");
        return str;
    }

    /**
     * Stellt eine List in Array- Matrix als String dar
     * @param aMatrix
     * @return void
     */

    public void toStringListInArray(List[] aMatrix){
        out("ListInArray-Matrix{ ");
        for (int i = 0; i < aMatrix.length; i++)
            out("[" + i + "] [" + "] = " + aMatrix[i].toString());
    }

    /**
     * Stellt eine List in List - Matrix als String dar
     * @param aMatrix
     * @return void
     */

    public void toStringList(List<List<Double>> aMatrix){
        out("List-Matrix{ ");
        for (int i = 0; i < aMatrix.size(); i++)
            out("[" + i + "] [" + "] = " + aMatrix.get(i).toString());
    }
}
